/* -------------------------------------------------------------------------------

This code is based on source provided under the terms of the Id Software 
LIMITED USE SOFTWARE LICENSE AGREEMENT, a copy of which is included with the
GtkRadiant sources (see LICENSE_ID). If you did not receive a copy of 
LICENSE_ID, please contact Id Software immediately at info@idsoftware.com.

All changes and additions to the original source which have been developed by
other contributors (see CONTRIBUTORS) are provided under the terms of the
license the contributors choose (see LICENSE), to the extent permitted by the
LICENSE_ID. If you did not receive a copy of the contributor license,
please contact the GtkRadiant maintainers at info@gtkradiant.com immediately.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS IS''
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY
DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.

----------------------------------------------------------------------------------

This code has been altered significantly from its original form, to support
several games based on the Quake III Arena engine, in the form of "Q3Map2."

------------------------------------------------------------------------------- */



/* marker */
#define IMAGE_C



/* dependencies */
#include "q3map2.h"



/* -------------------------------------------------------------------------------

this file contains image pool management with reference counting. note: it isn't
reentrant, so only call it from init/shutdown code or wrap calls in a mutex

------------------------------------------------------------------------------- */

/*
LoadDDSBuffer()
loads a dxtc (1, 3, 5) dds buffer into a valid rgba image
*/

static void LoadDDSBuffer( byte *buffer, int size, byte **pixels, int *width, int *height )
{
	int		w, h;
	ddsPF_t	pf;
	
	
	/* dummy check */
	if( buffer == NULL || size <= 0 || pixels == NULL || width == NULL || height == NULL )
		return;
	
	/* null out */
	*pixels = 0;
	*width = 0;
	*height = 0;
	
	/* get dds info */
	if( DDSGetInfo( (ddsBuffer_t*) buffer, &w, &h, &pf ) )
	{
		Sys_Printf( "WARNING: Invalid DDS texture\n" );
		return;
	}
	
	/* only certain types of dds textures are supported */
	if( pf != DDS_PF_ARGB8888 && pf != DDS_PF_DXT1 && pf != DDS_PF_DXT3 && pf != DDS_PF_DXT5 )
	{
		Sys_Printf( "WARNING: Only DDS texture formats ARGB8888, DXT1, DXT3, and DXT5 are supported (%d)\n", pf );
		return;
	}
	
	/* create image pixel buffer */
	*width = w;
	*height = h;
	*pixels = safe_malloc( w * h * 4 );
	
	/* decompress the dds texture */
	DDSDecompress( (ddsBuffer_t*) buffer, *pixels );
}



/*
PNGReadData()
callback function for libpng to read from a memory buffer
note: this function is a total hack, as it reads/writes the png struct directly!
*/

typedef struct pngBuffer_s
{
	byte	*buffer;
	int		size, offset;
}
pngBuffer_t;

void PNGReadData( png_struct *png, png_byte *buffer, png_size_t size )
{
	pngBuffer_t		*pb = (pngBuffer_t*) png_get_io_ptr( png );
	
	
	if( (pb->offset + size) > pb->size )
		size = (pb->size - pb->offset);
	memcpy( buffer, &pb->buffer[ pb->offset ], size );
	pb->offset += size;
	//%	Sys_Printf( "Copying %d bytes from 0x%08X to 0x%08X (offset: %d of %d)\n", size, &pb->buffer[ pb->offset ], buffer, pb->offset, pb->size );
}



/*
LoadPNGBuffer()
loads a png file buffer into a valid rgba image
*/

static void LoadPNGBuffer( byte *buffer, int size, byte **pixels, int *width, int *height )
{
	png_struct	*png;
	png_info	*info, *end;
	pngBuffer_t	pb;
	int			i, bitDepth, colorType, channels;
	png_uint_32	w, h;
	byte		**rowPointers;
	
	
	/* dummy check */
	if( buffer == NULL || size <= 0 || pixels == NULL || width == NULL || height == NULL )
		return;
	
	/* null out */
	*pixels = 0;
	*width = 0;
	*height = 0;
	
	/* determine if this is a png file */
	if( png_sig_cmp( buffer, 0, 8 ) != 0 )
	{
		Sys_Printf( "WARNING: Invalid PNG file\n" );
		return;
	}
	
	/* create png structs */
	png = png_create_read_struct( PNG_LIBPNG_VER_STRING, NULL, NULL, NULL );
	if( png == NULL )
	{
		Sys_Printf( "WARNING: Unable to create PNG read struct\n" );
		return;
	}
	
	info = png_create_info_struct( png );
	if( info == NULL )
	{
		Sys_Printf( "WARNING: Unable to create PNG info struct\n" );
		png_destroy_read_struct( &png, NULL, NULL );
		return;
	}
	
	end = png_create_info_struct( png );
	if( end == NULL )
	{
		Sys_Printf( "WARNING: Unable to create PNG end info struct\n" );
		png_destroy_read_struct( &png, &info, NULL );
		return;
	}
	
	/* set read callback */
	pb.buffer = buffer;
	pb.size = size;
	pb.offset = 0;
	png_set_read_fn( png, &pb, PNGReadData );
	png->io_ptr = &pb; /* hack! */
	
	/* set error longjmp */
	if( setjmp( png->jmpbuf ) )
	{
		Sys_Printf( "WARNING: An error occurred reading PNG image\n" );
		png_destroy_read_struct( &png, &info, &end );
		return;
	}
	
	/* fixme: add proper i/o stuff here */

	/* read png info */
	png_read_info( png, info );
	
	/* read image header chunk */
	png_get_IHDR( png, info,
		&w, &h, &bitDepth, &colorType, NULL, NULL, NULL );
	
	/* read number of channels */
	channels = png_get_channels( png, info );
	
	/* the following will probably bork on certain types of png images, but hey... */

	/* force indexed/gray/trans chunk to rgb */
	if( (colorType == PNG_COLOR_TYPE_PALETTE && bitDepth <= 8) ||
		(colorType == PNG_COLOR_TYPE_GRAY && bitDepth <= 8) ||
		png_get_valid( png, info, PNG_INFO_tRNS ) )
		png_set_expand( png );
	
	/* strip 16bpc -> 8bpc */
	if( bitDepth == 16 )
		png_set_strip_16( png );
	
	/* pad rgb to rgba */
	if( bitDepth == 8 && colorType == PNG_COLOR_TYPE_RGB)
		png_set_filler( png, 255, PNG_FILLER_AFTER );
	
	/* create image pixel buffer */
	*width = w;
	*height = h;
	*pixels = safe_malloc( w * h * 4 );
	
	/* create row pointers */
	rowPointers = safe_malloc( h * sizeof( byte* ) );
	for( i = 0; i < h; i++ )
		rowPointers[ i ] = *pixels + (i * w * 4);
	
	/* read the png */
	png_read_image( png, rowPointers );
	
	/* clean up */
	free( rowPointers );
	png_destroy_read_struct( &png, &info, &end );
	
}



/*
ImageInit()
implicitly called by every function to set up image list
*/

static void ImageInit( void )
{
	int		i;
	
	
	if( numImages <= 0 )
	{
		/* clear images (fixme: this could theoretically leak) */
		memset( images, 0, sizeof( images ) );
		
		/* generate *bogus image */
		images[ 0 ].name = safe_malloc( strlen( DEFAULT_IMAGE ) + 1 );
		strcpy( images[ 0 ].name, DEFAULT_IMAGE );
		images[ 0 ].filename = safe_malloc( strlen( DEFAULT_IMAGE ) + 1 );
		strcpy( images[ 0 ].filename, DEFAULT_IMAGE );
		images[ 0 ].width = 64;
		images[ 0 ].height = 64;
		images[ 0 ].refCount = 1;
		images[ 0 ].pixels = safe_malloc( 64 * 64 * 4 );
		for( i = 0; i < (64 * 64 * 4); i++ )
			images[ 0 ].pixels[ i ] = 255;
	}
}



/*
ImageFree()
frees an rgba image
*/

void ImageFree( image_t *image )
{
	/* dummy check */
	if( image == NULL )
		return;
	
	/* decrement refcount */
	image->refCount--;
	
	/* free? */
	if( image->refCount <= 0 )
	{
		if( image->name != NULL )
			free( image->name );
		image->name = NULL;
		if( image->filename != NULL )
			free( image->filename );
		image->filename = NULL;
		free( image->pixels );
		image->width = 0;
		image->height = 0;
		numImages--;
	}
}



/*
ImageFind()
finds an existing rgba image and returns a pointer to the image_t struct or NULL if not found
*/

image_t *ImageFind( const char *filename )
{
	int			i;
	char		name[ 1024 ];
	
	
	/* init */
	ImageInit();
	
	/* dummy check */
	if( filename == NULL || filename[ 0 ] == '\0' )
		return NULL;
	
	/* strip file extension off name */
	strcpy( name, filename );
	StripExtension( name );
	
	/* search list */
	for( i = 0; i < MAX_IMAGES; i++ )
	{
		if( images[ i ].name != NULL && !strcmp( name, images[ i ].name ) )
			return &images[ i ];
	}
	
	/* no matching image found */
	return NULL;
}



/*
ImageLoad()
loads an rgba image and returns a pointer to the image_t struct or NULL if not found
*/

image_t *ImageLoad( const char *filename )
{
	int			i;
	image_t		*image;
	char		name[ 1024 ];
	int			size;
	byte		*buffer = NULL;

	
	/* init */
	ImageInit();
	
	/* dummy check */
	if( filename == NULL || filename[ 0 ] == '\0' )
		return NULL;
	
	/* strip file extension off name */
	strcpy( name, filename );
	StripExtension( name );
	
	/* try to find existing image */
	image = ImageFind( name );
	if( image != NULL )
	{
		image->refCount++;
		return image;
	}
	
	/* none found, so find first non-null image */
	image = NULL;
	for( i = 0; i < MAX_IMAGES; i++ )
	{
		if( images[ i ].name == NULL )
		{
			image = &images[ i ];
			break;
		}
	}
	
	/* too many images? */
	if( image == NULL )
		Error( "MAX_IMAGES (%d) exceeded, there are too many image files referenced by the map.", MAX_IMAGES );
	
	/* set it up */
	image->name = safe_malloc( strlen( name ) + 1 );
	strcpy( image->name, name );
	
	/* attempt to load tga */
	StripExtension( name );
	strcat( name, ".tga" );
	size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 );
	if( size > 0 )
		LoadTGABuffer( buffer, &image->pixels, &image->width, &image->height );
	else
	{
		/* attempt to load png */
		StripExtension( name );
		strcat( name, ".png" );
		size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 );
		if( size > 0 )
			LoadPNGBuffer( buffer, size, &image->pixels, &image->width, &image->height );
		else
		{
			/* attempt to load jpg */
			StripExtension( name );
			strcat( name, ".jpg" );
			size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 );
			if( size > 0 )
			{
				if( LoadJPGBuff( buffer, size, &image->pixels, &image->width, &image->height ) == -1 && image->pixels != NULL )
					Sys_Printf( "WARNING: LoadJPGBuff: %s\n", (unsigned char*) image->pixels );
			}
			else
			{
				/* attempt to load dds */
				StripExtension( name );
				strcat( name, ".dds" );
				size = vfsLoadFile( (const char*) name, (void**) &buffer, 0 );
				if( size > 0 )
				{
					LoadDDSBuffer( buffer, size, &image->pixels, &image->width, &image->height );
					
					/* debug code */
					#if 1
					{
						ddsPF_t	pf;
						DDSGetInfo( (ddsBuffer_t*) buffer, NULL, NULL, &pf );
						Sys_Printf( "pf = %d\n", pf );
						if( image->width > 0 )
						{
							StripExtension( name );
							strcat( name, "_converted.tga" );
							WriteTGA( "C:\\games\\quake3\\baseq3\\textures\\rad\\dds_converted.tga", image->pixels, image->width, image->height );
						}
					}
					#endif
				}
			}
		}
	}
	
	/* free file buffer */
	free( buffer );
	
	/* make sure everything's kosher */
	if( size <= 0 || image->width <= 0 || image->height <= 0 || image->pixels == NULL )
	{
		//%	Sys_Printf( "size = %d  width = %d  height = %d  pixels = 0x%08x (%s)\n",
		//%		size, image->width, image->height, image->pixels, name );
		free( image->name );
		image->name = NULL;
		return NULL;
	}
	
	/* set filename */
	image->filename = safe_malloc( strlen( name ) + 1 );
	strcpy( image->filename, name );
	
	/* set count */
	image->refCount = 1;
	numImages++;
	
	/* return the image */
	return image;
}


